package rad.framework.mda.jpagen.actions;

import rad.framework.mda.generator.Console;
import rad.framework.mda.util.ModelUtil;

import org.eclipse.emf.common.util.EList;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Enumeration;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Stereotype;

/**
 * @author boudyacho
 * 
 */
public class CleanupAssociationsActionExtension extends
		BaseUtiltiyActionExtension {

	protected static final String MAPPED_BY = "mappedBy";

	protected static final String FETCH = "fetch";

	protected static final String FETCH_TYPE = "javax.persistence.FetchType";

	protected static final String CASCADE = "cascade";

	protected static final String CASCADE_TYPE = "javax.persistence.CascadeType";

	protected static final String LAZY = "LAZY";

	protected static final String ALL = "ALL";

	protected static final String MERGE = "MERGE";

	protected static final String O2M = "javax.persistence.OneToMany";

	protected static final String M2O = "javax.persistence.ManyToOne";

	protected static final String O2O = "javax.persistence.OneToOne";

	protected static final String M2M = "javax.persistence.ManyToMany";

	public void fillContextMenu(IMenuManager aMenu) {
		getSubMenu(aMenu).add(new CleanupAssociationsAction());
	}

	protected void execute(Class clazz) {
		EList<NamedElement> list = clazz.getMembers();
		for (NamedElement namedElement : list) {
			if (namedElement instanceof Property) {
				execute((Property) namedElement);
			}
		}
	}

	protected void execute(Property property) {
		// TODO define a way to plugin these rules in a profile
		// plugin.xml file
		if (ModelUtil.isStereotypeApplied(property, O2M)) {
			cleanupOneToMany(property);
		}
		if (ModelUtil.isStereotypeApplied(property, M2O)) {
			cleanupManyToOne(property);
		}
		if (ModelUtil.isStereotypeApplied(property, M2M)) {
			cleanupManyToMany(property);
		}
		if (ModelUtil.isStereotypeApplied(property, O2O)) {
			cleanupOneToOne(property);
		}
	}

	protected void cleanupOneToMany(Property property) {
		Stereotype stereotype = ModelUtil.getAppliedStereotype(property, O2M);
		String mappedBy = (String) property.getValue(stereotype, MAPPED_BY);
		if (isEmpty(mappedBy)) {
			setLazy(property, stereotype);
		}
		if (property.isComposite()) {
			setCascadeAll(property, stereotype);
		} else {
			setMerge(property, stereotype);
		}
		property.setValue(stereotype, MAPPED_BY, property.getOpposite()
				.getName());
		Console.getStream().println(
				"OneToMany Cleanup - " + property.getQualifiedName());
	}

	protected void cleanupManyToOne(Property property) {
		Stereotype stereotype = ModelUtil.getAppliedStereotype(property, M2O);
		// TODO need condition?
		setMerge(property, stereotype);
		setLazy(property, stereotype);
		Console.getStream().println(
				"ManyToOne Cleanup - " + property.getQualifiedName());
	}

	protected void cleanupManyToMany(Property property) {
		Stereotype stereotype = ModelUtil.getAppliedStereotype(property, M2M);
		String mappedBy1 = (String) property.getValue(stereotype, MAPPED_BY);
		Stereotype opposite = null;
		String mappedBy2 = null;
		if (property.getOpposite() != null) {
			opposite = ModelUtil.getAppliedStereotype(property
					.getOpposite(), M2M);
			mappedBy2 = (String) property.getOpposite().getValue(opposite,
					MAPPED_BY);
		}
		if (isEmpty(mappedBy1) && isEmpty(mappedBy2)) {
			setLazy(property, stereotype);
			setMerge(property, stereotype);
			if (opposite != null) {
				setLazy(property.getOpposite(), opposite);
				setMerge(property.getOpposite(), opposite);
			}
		}
		
		if (isRefresh(property, stereotype)) {
			setMerge(property, stereotype);
			if (opposite != null) {
				setMerge(property.getOpposite(), opposite);
			}
		}
		
		// TODO this is probably backwards, same for JoinTable
		if (isSource(property) && property.getOpposite() != null) {
			property.setValue(stereotype, MAPPED_BY, property.getOpposite()
					.getName());
			Console.getStream().println(
					"ManyToMany Cleanup - " + property.getQualifiedName());
		} else if (!isEmpty(mappedBy1)) {
			property.setValue(stereotype, MAPPED_BY, "");
			Console.getStream().println(
					"ManyToMany Cleanup - " + property.getQualifiedName());
		}
	}

	protected void cleanupOneToOne(Property property) {
		Stereotype stereotype = ModelUtil.getAppliedStereotype(property, O2O);
		String mappedBy1 = (String) property.getValue(stereotype, MAPPED_BY);
		Stereotype opposite = ModelUtil.getAppliedStereotype(property
				.getOpposite(), O2O);
		String mappedBy2 = (String) property.getOpposite().getValue(opposite,
				MAPPED_BY);
		if (isEmpty(mappedBy1) && isEmpty(mappedBy2)) {
			setLazy(property, stereotype);
			setMerge(property, stereotype);
			setLazy(property.getOpposite(), opposite);
			setMerge(property.getOpposite(), opposite);
		}
		if (isSource(property)) {
			property.setValue(stereotype, MAPPED_BY, property.getOpposite()
					.getName());
			Console.getStream().println(
					"OneToOne Cleanup - " + property.getQualifiedName());
		} else if (!isEmpty(mappedBy1)) {
			property.setValue(stereotype, MAPPED_BY, "");
			Console.getStream().println(
					"OneToOne Cleanup - " + property.getQualifiedName());
		}
	}

	protected void setLazy(Element element, Stereotype stereotype) {
		Element e = ModelUtil.getProfileElement(element, FETCH_TYPE);
		if (e != null) {
			if (e instanceof Enumeration) {
				element.setValue(stereotype, FETCH, ((Enumeration) e)
						.getOwnedLiteral(LAZY));
			}
		}
	}

	protected void setCascadeAll(Element element, Stereotype stereotype) {
		Element e = ModelUtil.getProfileElement(element, CASCADE_TYPE);
		if (e != null) {
			if (e instanceof Enumeration) {
				element.setValue(stereotype, CASCADE, ((Enumeration) e)
						.getOwnedLiteral(ALL));
			}
		}
	}

	protected void setMerge(Element element, Stereotype stereotype) {
		Element e = ModelUtil.getProfileElement(element, CASCADE_TYPE);
		if (e != null) {
			if (e instanceof Enumeration) {
				element.setValue(stereotype, CASCADE, ((Enumeration) e)
						.getOwnedLiteral(MERGE));
			}
		}
	}

	protected boolean isRefresh(Element element, Stereotype stereotype) {
		Element e = ModelUtil.getProfileElement(element, CASCADE_TYPE);
		if (e != null) {
			if (e instanceof Enumeration) {
				Object value = element.getValue(stereotype, CASCADE);
				return ((Enumeration) e).getOwnedLiteral("REFRESH").equals(
						value);
			}
		}
		return false;
	}

	private class CleanupAssociationsAction extends BaseUtilityAction {
		private static final String LABEL = "Cleanup Associations";

		public CleanupAssociationsAction() {
			super(LABEL);
		}
	}
}
